#!/usr/bin/env python3
# coding: utf-8
# Copyright 2024 Huawei Technologies Co., Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
# ===========================================================================
import os
import re

from ansible.module_utils.check_output_manager import check_event
from ansible.module_utils.check_utils import CheckUtil as util
from ansible.module_utils.common_utils import result_handler, compare_version


class DifyCheck:
    min_docker_version = '18.03'
    min_docker_compose_version = '1.28'

    def __init__(self, module, error_messages):
        self.module = module
        self.tags = list(filter(bool, self.module.params.get('tags', [])))
        self.dify_code_path = module.params["dify_code_path"]
        self.error_messages = error_messages

    def check_docker_version(self):
        if not self.module.get_bin_path("docker"):
            if "sys_pkg" not in self.tags:
                util.record_error('[ASCEND][ERROR] Please install docker first.',
                                  self.error_messages)
            return
        rc, out, _ = self.module.run_command("docker --version")
        if rc != 0:
            util.record_error('[ASCEND][ERROR] Please confirm that the Docker status is healthy.',
                              self.error_messages)
        docker_version = re.search(r"\d+\.\d+\.\d+", out).group()
        if compare_version(docker_version, self.min_docker_version) < 0:
            util.record_error('[ASCEND][ERROR] Docker version should be >= {}'.format(self.min_docker_version),
                              self.error_messages)

    def check_docker_compose_version(self):
        """
        检查 Docker Compose 版本（优先独立 docker-compose 命令，其次集成 docker compose 命令）：
        1. 优先检查独立 docker-compose 命令（支持 v1/v2 独立安装包）
        - 若为 v1 版本：必须满足最低版本要求（>= 1.28）
        - 若为 v2 版本：直接通过（官方 v2 均满足现代需求）
        2. 若独立命令不存在/不满足要求，检查集成式 docker compose 命令（官方 v2）
        - 存在则直接通过，不存在则提示安装
        """
        # 标记是否找到有效且合规的 Docker Compose 版本
        valid_compose_found = False
        # 1. 优先检查独立 docker-compose 命令（覆盖 v1 独立包、v2 独立包场景）
        docker_compose_path = self.module.get_bin_path("docker-compose")
        if docker_compose_path:
            # 执行独立命令获取版本信息
            rc, out, err = self.module.run_command("docker-compose --version")
            if rc == 0:
                # 匹配版本号（兼容 v1 格式如 "1.29.2"、v2 格式如 "v2.20.2"）
                version_match = re.search(r"v?(\d+\.\d+\.\d+)", out)
                if version_match:
                    compose_version = version_match.group(1)
                    # 判断是否为 v1 版本（主版本号为 1 则视为 v1）
                    is_v1 = compose_version.startswith("1.")
                    
                    if is_v1:
                        # v1 版本必须满足最低版本要求
                        if compare_version(compose_version, self.min_docker_compose_version) >= 0:
                            # v1 版本合规，标记有效
                            valid_compose_found = True
                        else:
                            # v1 版本过低，记录错误（但不终止，后续检查集成命令）
                            util.record_error(
                                '[ASCEND][ERROR] Docker Compose v1 version {} is too low. '
                                'Minimum required version: {}'.format(compose_version, self.min_docker_compose_version),
                                self.error_messages
                            )
                    else:
                        # 独立命令的 v2 版本（如 docker-compose v2.20.2），直接合规
                        valid_compose_found = True
                else:
                    # 无法解析独立命令的版本，记录错误（后续检查集成命令）
                    util.record_error(
                        '[ASCEND][ERROR] Failed to parse version from standalone docker-compose command. Output: {}'.format(out.strip()),
                        self.error_messages
                    )
            else:
                # 独立命令存在但执行失败（如损坏），记录错误（后续检查集成命令）
                util.record_error(
                    '[ASCEND][ERROR] Standalone docker-compose command exists but execution failed. Error: {}'.format(err.strip()),
                    self.error_messages
                )
        
        # 2. 若独立命令未找到有效版本，检查集成式 docker compose 命令（官方 v2）
        if not valid_compose_found:
            # 先确认 Docker 基础命令存在（集成命令依赖 Docker 主程序）
            if not self.module.get_bin_path("docker"):
                # Docker 未安装，无法使用集成命令，记录错误
                util.record_error(
                    '[ASCEND][ERROR] Docker is not installed. Cannot use integrated "docker compose" command.',
                    self.error_messages
                )
                return
            
            # 执行集成命令获取版本信息
            rc, out, err = self.module.run_command("docker compose version")
            if rc == 0:
                # 集成命令存在且正常，标记有效（官方 v2 无需额外版本校验）
                valid_compose_found = True
            else:
                # 集成命令执行失败（如 Docker 未启动、无 compose 插件），记录最终错误
                util.record_error(
                    '[ASCEND][ERROR] Neither valid standalone docker-compose nor integrated "docker compose" found. '
                    'Suggestions: \n1. Install standalone Docker Compose v1 (>= {}) \n2. Install Docker with Compose plugin (v2+)'.format(
                        self.min_docker_compose_version
                    ),
                    self.error_messages
                )


    def check_dify_code_path(self):
        if not self.dify_code_path:
            util.record_error('[ASCEND][ERROR] Please provide a value for the dify_code_path parameter.',
                              self.error_messages)

    def check_dify_container_exist(self):
        """
        检查系统中是否存在镜像名包含 "dify" 的 Docker 容器，提示可能已安装 Dify
        """
        # 前置检查：Docker 是否安装（和原有逻辑一致）
        if not self.module.get_bin_path("docker"):
            return
        
        # 执行 Docker 命令：列出所有容器的「镜像名」和「容器名」（便于日志展示）
        # 命令说明：
        # - ps -a：所有容器（运行中+已停止）
        # - --format "{{.Image}}|{{.Names}}"：输出格式：镜像名|容器名（用分隔符区分，方便解析）
        rc, out, err = self.module.run_command(
            ["docker", "ps", "-a", "--format", "{{.Image}}|{{.Names}}"]
        )
        
        # 命令执行失败处理
        if rc != 0:
            util.record_error('[ASCEND][ERROR] Failed to query Docker containers. Error: {}'.format(err.strip()),
                            self.error_messages)
            return
        
        # 标记是否找到含 dify 的容器
        dify_container_found = False
        # 存储找到的相关容器信息（镜像名+容器名）
        dify_containers = []
        
        # 解析命令输出（每行对应一个容器）
        container_lines = out.splitlines()
        for line in container_lines:
            if not line.strip():
                continue  # 跳过空行（避免异常）
            
            # 按分隔符分割镜像名和容器名（处理可能的分隔符前后空格）
            parts = [p.strip() for p in line.split('|', 1)]  # split('|',1) 只分割一次，避免镜像名含 | 的极端情况
            if len(parts) != 2:
                continue
            
            image_name, container_name = parts
            
            # 检查镜像名是否包含 "dify"（不区分大小写，避免漏检 Dify/DIFY 等情况）
            if "dify" in image_name.lower():
                dify_container_found = True
                dify_containers.append({
                    "image": image_name,
                    "name": container_name
                })
        
        # 结果处理：找到则提示，未找到则记录info日志
        if dify_container_found:
            # 拼接容器信息日志（便于用户查看）
            container_info = "\n".join([
                "- 容器名: {}, 镜像名: {}".format(c["name"], c["image"])
                for c in dify_containers
            ])
            util.record_error(
                '[ASCEND][ERROR] Found containers with "dify" in their image name. It is possible that Dify has been installed.\n'
                'Related containers:\n{}'.format(container_info),
                self.error_messages
            )
        



    @check_event
    def check_dify(self):
        self.check_docker_version()
        self.check_dify_code_path()
        self.check_dify_container_exist()
        self.check_docker_compose_version()
